/********************************************************************/
/********************** File sim.c **********************************/
#include "c-sim.h"

int main() {
	double global_time = 0.0;
	int process, event;
	init();

	/***** Main simulation loop *****/
	while (global_time <= TTotal) {
		/***** Select the event e from the head of event list *****/
		process = elist.task[elist.head];
		global_time = elist.time[elist.head];
		event = elist.event[elist.head];
		elist.head = (elist.head + 1) % N;
		elist.q--;
		/***** Execute the event e ******/
		//cout << "This is OK here" << endl;
		switch (event) {
		case RequestMemory:
			Process_RequestMemory(process, global_time);
			//cout << "This is OK request memory " << endl;
			break;
		case RequestCPU:
			Process_RequestCPU(process, global_time);
			//cout << "This is OK  request CPU " << endl;
			break;
		case ReleaseCPU:
			Process_ReleaseCPU(process, global_time);
			//cout << "This is OK release CPU " << endl;
			break;
		case RequestDisk:
			Process_RequestDisk(process, global_time);
			//cout << "This is OK request disk " << endl;
			break;
		case ReleaseDisk:
			Process_ReleaseDisk(process, global_time);
			//cout << "This is OK release disk " << endl;
			break;
		}
	}
	stats();
	return 1;
}

/********************************************************************/
/********************* Event Functions ******************************/
/********************************************************************/
void Process_RequestMemory(int process, double time) {
	/**** Create a Process_RequestCPU event or place a task in memory queue      ****/
	if (inmemory < MPL) {
		inmemory++;
		/**** Generate the time properties of the task ****/
		task[process].tcpu = random_exponential(TCPU);
		task[process].tquantum = TQuantum;
		task[process].t_io = random_exponential(TIO);
		task[process].t_page = random_exponential(TPAGE);
		create_event(process, RequestCPU, time, LowPriority);
	} else {
		place_in_queue(process, time, MemoryQueue);
	}
}

void Process_RequestCPU(int process, double time) {
	vector<int> free_processor;
	int cpu_num;
	double release_time;
	/**** Place in CPU queue if server is busy ****/
	int flag = 0;
	for (int i = 0; i < num_CPU; i++) {
		if (cpu[i].busy) {
			flag++;
		} else {
			free_processor.push_back(i);
		}
	}

	//cout << "The flag is: " << flag << endl;
	if (flag == num_CPU)
		place_in_queue(process, time, CPUQueue);
	else {
		//change: random algorithm needed to be changed
		cpu_num = random_server_num(-1, free_processor.size());
		int index = free_processor[cpu_num];
		//cout << "The index is: " << index << endl;
		cpu[index].busy = 1;
		cpu[index].tch = time;
		task[process].cpu_id = index;

		/*******find min(tcpu, tq, tpage, tio) *********/
		if (task[process].tcpu < task[process].tquantum) {
			release_time = task[process].tcpu;
		} else {
			release_time = task[process].tquantum;
		}

		if (release_time > task[process].t_page) {
			release_time = task[process].t_page;
		}

		if (release_time > task[process].t_io) {
			release_time = task[process].t_io;
		}

		/**** Update the process times and create Process_ReleaseCPU event           ****/
		task[process].tcpu -= release_time;
		task[process].t_page -= release_time;
		task[process].t_io -= release_time;
		task[process].tquantum -= release_time;

		create_event(process, ReleaseCPU, time + release_time, LowPriority);
	}
}

void Process_ReleaseCPU(int process, double time) {
	int queue_head;
	int index;

	index = task[process].cpu_id;

	//Miles: change | to ||
	if ((index < 0) || (index >= num_CPU)) {
		cout << "ERROR here!!!! for the cpu index" << endl;
	}

	/**** Update CPU statistics                                            ****/
	cpu[index].busy = 0;
	cpu[index].tser += (time - cpu[index].tch);

	queue_head = remove_from_queue(CPUQueue, time); /* remove head of CPU queue ****/
	if (queue_head != EMPTY) {
		create_event(queue_head, RequestCPU, time, HighPriority);
	}

	/**** Depending on reason for leaving CPU, select the next event

	 Find the time of leaving CPU from Comment on the slides 17 page 13,
	 order of zero counter selection is rather arbitrary.
	 different ones make sense, Assume tcput tq tio tpage order****/

	if (task[process].tcpu == 0) {
		/* task termination ****/
		// Response time of processes is from the beginning of eligible queue to the termination time
		sum_response_time += time - task[process].start;
		finished_tasks++;

		/* Miles: need to be fixed for different type: parallel and interactive */
		if (task[process].task_type == INTER) {
			task[process].start = time + random_exponential(TThink);
			create_event(process, RequestMemory, task[process].start,
					LowPriority);
			inmemory--;
			queue_head = remove_from_queue(MemoryQueue, time);
			if (queue_head != EMPTY) {
				create_event(queue_head, RequestMemory, time, HighPriority);
			}
		} else {
			barrier_status[process - N] = true;
			bool synStatus = checkSynchronizationStatus();

			//If reaching a synchronization point, then create ReqeustCPU for all parallel processes
			if (synStatus) {
				createParallelProcesses(time);
			}
		}
	} else if (task[process].tquantum == 0) {
		task[process].tquantum = TQuantum;

		//Miles: if parallel process, enter into ready queue, RequestCPU
		//if interactive process, enter into eligible queue, RequestMemory
		if (task[process].task_type == PARA) {
			create_event(process, RequestCPU, time, LowPriority);
		} else {
			create_event(process, RequestMemory, time, LowPriority);
			inmemory--;
			queue_head = remove_from_queue(MemoryQueue, time);
			if (queue_head != EMPTY) {
				create_event(queue_head, RequestMemory, time, HighPriority);
			}
		}
	} else {
		/* disk access interrupt  include both io and page fault ****/
		if (task[process].t_io == 0) {
			task[process].t_io = random_exponential(TIO);
			create_event(process, RequestDisk, time, LowPriority);
		} else if (task[process].t_page == 0) {
			task[process].t_page = random_exponential(TPAGE);
			create_event(process, RequestDisk, time, LowPriority);
		}
	}
}

void Process_RequestDisk(int process, double time) {
	/**** If Disk busy go to Disk queue, if not create Process_ReleaseDisk event    ****/
	vector<int> free_disk;
	int disk_num, index, flag = 0;

	for (int i = 0; i < num_disk; i++) {
		if (disk[i].busy) {
			flag++;
		} else {
			free_disk.push_back(i);
		}
	}

	if (flag == num_disk) {
		//change: need to specify which disk queue to put into
		place_in_queue(process, time, DiskQueue);
	} else {
		//change: assign at first. Do not need to do here
		disk_num = random_server_num(-1, free_disk.size());
		index = free_disk[disk_num];
		disk[index].busy = 1;
		disk[index].tch = time;
		task[process].disk_id = index;
		create_event(process, ReleaseDisk,
				time + random_exponential(TDiskService), LowPriority);
	}
}

void Process_ReleaseDisk(int process, double time) {
	int disk_queue_head;
	/**** Update statistics for Disk and create Process_RequestCPU event         ****/
	int index;
	index = task[process].disk_id;

	//Miles: change | to ||
	if ((index < 0) || (index >= num_disk))
		cout << "ERROR the disk index " << endl;
	/**** Update CPU statistics                                            ****/
	disk[index].busy = 0;
	disk[index].tser += (time - disk[index].tch);
	disk_queue_head = remove_from_queue(DiskQueue, time);
	if (disk_queue_head != EMPTY) {
		create_event(disk_queue_head, RequestDisk, time, HighPriority);
	}
	create_event(process, RequestCPU, time, LowPriority);
}

/********************************************************************/
/******************* Auxiliary Functions ****************************/
/********************************************************************/

int remove_from_queue(int current_queue, double time) {
	int process;
	/**** If queue not empty, remove the head of the queue              ****/
	if (queue[current_queue].q > 0) {
		process = queue[current_queue].task[queue[current_queue].head];
		/**** Update statistics for the queue                               ****/
		queue[current_queue].ws += time
				- queue[current_queue].tentry[queue[current_queue].head];
		queue[current_queue].ts += (time - queue[current_queue].tch)
				* queue[current_queue].q;
		queue[current_queue].q--;
		queue[current_queue].tch = time;

		/**** Create a new event for the task at the head and move the head ****/
		queue[current_queue].head = (queue[current_queue].head + 1) % N;
		return (process);
	} else {
		return (EMPTY);
	}
}

void place_in_queue(int process, double time, int current_queue) {
	/**** Update statistics for the queue                               ****/
	queue[current_queue].ts += (time - queue[current_queue].tch)
			* queue[current_queue].q;
	queue[current_queue].q++;
	queue[current_queue].n++;
	queue[current_queue].tch = time;

	/**** Place the process at the tail of queue and move the tail       ****/
	queue[current_queue].task[queue[current_queue].tail] = process;
	queue[current_queue].tentry[queue[current_queue].tail] = time;
	queue[current_queue].tail = (queue[current_queue].tail + 1) % N;
}

void create_event(int process, int event, double time, int priority) {
	int i, notdone = 1, place = elist.tail;

	/**** Move all more futuristic tasks by one position  ****/
	//notdone = 1 means that it still does not find the position that place the process
	for (i = (elist.tail + N - 1) % N; notdone && (elist.q > 0);
			i = (i + N - 1) % N) {

		//Miles: change from | to ||, from & to &&
		if ((elist.time[i] < time)
				|| ((priority == LowPriority) && (elist.time[i] == time))) {
			notdone = 0;
		} else {
			elist.time[place] = elist.time[i];
			elist.task[place] = elist.task[i];
			elist.event[place] = elist.event[i];
			place = i;
		}
		if (i == elist.head) {
			notdone = 0;
		}
	}

	/**** Place the argument event in the newly created space           ****/
	elist.time[place] = time;
	elist.task[place] = process;
	elist.event[place] = event;
	elist.tail = (elist.tail + 1) % N;
	elist.q++;
}

double random_exponential(double y) {
	static unsigned long w = 0L, g, m, x;
	/**** Better random function ?? (found on the internet :)           ****/
	if (w == 0L) {
		w = sizeof(long) * 8;
		g = (1L << ((w - 1) - 1));
		m = (1L << ((w / 2) - 1)) + 3;
		x = (1L << ((w / 4) - 1)) + 1;
	}
	x = (x * m) % g;
	return -y * log((double) x / g);
}

void init() {
	int i;
	/**** Initialize structures  ****/
	elist.head = elist.tail = elist.q = 0;

	//initialize the properties of three queue
	for (i = 0; i < 3; i++) {
		queue[i].head = queue[i].tail = queue[i].q = queue[i].n = 0;
		queue[i].ws = queue[i].ts = 0.0;
		queue[i].tch = 0;
	}

	//initialize the properties of cpus
	for (i = 0; i < num_CPU; i++) {
		cpu[i].busy = 0;
		cpu[i].tch = cpu[i].tser = 0.0;
	}

	//initialize the properties of disks
	for (i = 0; i < num_disk; i++) {
		disk[i].busy = 0;
		disk[i].tch = disk[i].tser = 0.0;
	}

	//create N interactive processes
	//change: need to sepcify its disk and also type
	for (i = 0; i < N; i++) {
		task[i].start = random_exponential(TThink);
		task[i].cpu_id = -100;
		task[i].disk_id = -100;
		create_event(i, RequestMemory, task[i].start, LowPriority);
	}

	//Miles: create ppnum parallel processes and execute immediately
	createParallelProcesses(0);
	//question: when there is no enough memory for all 6 parallel processes, what should be done next?
	inmemory += ppnum;
}

//Miles: create ppnum parallel processes
/**paparameter:
 * @time: the time that the parallel processes start to request cpu
 */
void createParallelProcesses(double time) {

	//Because the start time is 0; thus, at first, they will be in the first of the event list
	for (int i = N; i < N + ppnum; ++i) {
		/*The number of processes in memory plus one
		 Because when parallel process created, it enter into the CPU queue immediately
		 */
		task[i].task_type = PARA;
		task[i].start = time;
		task[i].tcpu = random_exponential(TBS);
		task[i].tquantum = TQuantum;
		task[i].t_io = random_exponential(TIO);
		task[i].t_page = random_exponential(TPAGE);
		task[i].cpu_id = -100;
		//change: need to be changed
		task[i].disk_id = -100;
		create_event(i, RequestCPU, task[i].start, LowPriority);

		//set the status of barrier synchronization
		barrier_status[i - N] = false;
	}
}

// check whether all 6 parallel processes are in the BS
bool checkSynchronizationStatus() {
	bool synStatus = true;

	for (int i = 0; i < ppnum; ++i) {
		if (!barrier_status[i]) {
			synStatus = false;
			break;
		}
	}

	return synStatus;
}

//change: only random in the free cpus
int random_server_num(int max, int min) {
	return min + ((double) rand() / (RAND_MAX)) * (max - min + 1);
}

void stats() {
	/**** Update utilizations                                          ****/
	for (int i = 0; i < num_CPU; i++) {
		if (cpu[i].busy == 1)
			cpu[i].tser += (TTotal - cpu[i].tch);

		//printf("utilizations are: CPU[%d]: %5.2f Disk[%d]: %5.2f\n",
		//       100.0*server[0].tser/TTotal, 100.0*server[1].tser/TTotal);
		printf("utilizations are: CPU[%d]: %f \n", i,
				100.0 * cpu[i].tser / TTotal);
	}

	for (int i = 0; i < num_disk; i++) {
		if (disk[i].busy == 1)
			disk[i].tser += (TTotal - disk[i].tch);
		printf("utilizations are: disk[%d]: %f\n", i,
				100.0 * disk[i].tser / TTotal);
	}

	/**** Print statistics                                             ****/
	printf("mean waiting time in qe %5.2f qCPU %5.2f qDisk %5.2f\n",
			queue[0].ws ? queue[0].ws / (queue[0].n - queue[0].q) : 0.0,
			queue[1].ws ? queue[1].ws / (queue[1].n - queue[1].q) : 0.0,
			queue[2].ws ? queue[2].ws / (queue[2].n - queue[2].q) : 0.0);

	printf("mean queue length in qe %5.2f qCPU %5.2f qDisk %5.2f\n",
			//queue[0].tch?queue[0].ts/queue[0].tch:0.0,
			//queue[1].tch?queue[1].ts/queue[1].tch:0.0,
			queue[0].ts ? queue[0].ts / queue[0].tch : 0.0,
			queue[1].ts ? queue[1].ts / queue[1].tch : 0.0,
			queue[2].ts ? queue[2].ts / queue[2].tch : 0.0);

	cout << "number of n: " << queue[1].n << endl;
	cout << "number of q: " << queue[1].q << endl;
	printf("number of visits in qe  %5d qCPU %5d qDisk %5d\n",
			queue[0].n - queue[0].q, queue[1].n - queue[1].q,
			queue[2].n - queue[2].q);

	printf("average response time   %5.2f processes finished %5d\n",
			sum_response_time / finished_tasks, finished_tasks);
}

